package mosdi.paa.apps;

import java.util.Arrays;

import mosdi.fa.Partition;
import mosdi.paa.MinimizableDAA;
import mosdi.util.ProductEncoder;

/** DAA that counts the number of flows needed when sequencing a read
 *  with a dispensation order given at construction time (for sequencers
 *  such as 454 or IonTorrent). */
public class DispensationOrderDAA extends MinimizableDAA {
	// states are numbered as follows:
	// 0: (-1,-1)
	// 1: (-1, 0)
	// ...
	// l: (-1,l-1)
	// from l+1 as encoded in product encoder,
	// where l is the length of the dispensation order
	private ProductEncoder encoder;
	// transitionTable[j][c] is the flow index reached from flow index j
	// when reading character c.
	private int[][] transitionTable;
	private int alphabetSize;
	private int length;
	private int maxFlowCount;
	
	public DispensationOrderDAA(int alphabetSize, int[] dispensationOrder, int maxFlowCount) {
		this.alphabetSize = alphabetSize;
		this.length = dispensationOrder.length;
		this.encoder = new ProductEncoder(length, length);
		this.transitionTable = new int[length][alphabetSize];
		this.maxFlowCount = maxFlowCount;
		// iterate twice over dispensation order (backwards)
		for (int i=0; i<2; ++i) {
			for (int j=length-1; j>=0; --j) {
				for (int c=0; c<alphabetSize; ++c) {
					if (dispensationOrder[j] == c) {
						transitionTable[j][c] = j;
					} else {
						transitionTable[j][c] = transitionTable[(j+1)%length][c];
					}
				}
			}
		}
	}
	
	@Override
	public int getAlphabetSize() {
		return alphabetSize;
	}

	@Override
	public int getStartState() {
		return 0;
	}

	@Override
	public int getStateCount() {
		return 1 + length + length*length;
	}

	@Override
	public int getTransitionTarget(int state, int character) {
		if (state==0) {
			return 1 + transitionTable[0][character];	
		} else if (state<=length) {
			int j = state - 1;
			return length + 1 + encoder.encode(j, transitionTable[j][character]);
		} else {
			int j = encoder.decodeComponent(1, state - length - 1);
			return length + 1 + encoder.encode(j, transitionTable[j][character]);
		}
	}

	@Override
	public int getEmission(int state) {
		if (state==0) {
			return 0;
		} else if (state<=length) {
			return state;
		} else {
			int[] a = encoder.decode(state - length - 1);
			return (a[1] - a[0] + length) % length;
		}
	}

	@Override
	public int getEmissionCount() {
		return length;
	}

	@Override
	public int getStartValue() {
		return 0;
	}

	@Override
	public int getValueCount() {
		return maxFlowCount + 1;
	}

	@Override
	public int performOperation(int state, int value, int emission) {
		return Math.min(value + emission, maxFlowCount);
	}

	@Override
	public Partition getStatePartition() {
		Integer[] allEmissions = new Integer[getStateCount()];
		for (int i=0; i<getStateCount(); ++i) {
			allEmissions[i] = getEmission(i);
		}
		return new Partition(Arrays.asList(allEmissions));
	}

}
